iT邦幫忙

2025 iThome 鐵人賽

DAY 21
1
Modern Web

我與型別的 30 天約定:TypeScript 入坑實錄系列 第 21

Day 21|TypeScript 工具型別實戰:Pick / Omit / Partial / Required / Record / ReturnType 全解

  • 分享至 

  • xImage
  •  

在前面幾天,我們一直有用到一些工具型別(例如 Pick<User, "id" | "name">),

今天來做一次系統化整理,並用專案實例示範它們的實戰用途。


1) 為什麼要用工具型別?

  • 減少重複型別宣告:不用再手動重抄型別
  • 避免型別不一致:從源頭型別直接衍生
  • 更彈性:可以組合、變形、條件化

我們今天的例子會沿用 User 型別(Day 15 建立的資料模型):

ts
CopyEdit
type User = {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
};


2) Pick<T, K>:只取部分屬性

ts
CopyEdit
type UserSummary = Pick<User, "id" | "name">;

用途範例:API 列表只需要顯示使用者 ID 與名稱

ts
CopyEdit
function listUsers(): UserSummary[] {
  return [
    { id: "u1", name: "Alice" },
    { id: "u2", name: "Bob" },
  ];
}

心法:當 API 或 UI 只需要部分欄位時,用 Pick 直接從原型別取,避免自己寫新型別。


3) Omit<T, K>:排除部分屬性

ts
CopyEdit
type CreateUserInput = Omit<User, "id" | "createdAt" | "updatedAt">;

用途範例:新增使用者時,前端不用傳 id 和時間戳

ts
CopyEdit
async function createUser(payload: CreateUserInput) {
  return fetch("/users", {
    method: "POST",
    body: JSON.stringify(payload),
  });
}


4) Partial<T>:所有屬性改成可選

ts
CopyEdit
type UpdateUserInput = Partial<Omit<User, "id">>;

用途範例:更新使用者時,可以只更新部分欄位

ts
CopyEdit
async function updateUser(id: string, payload: UpdateUserInput) {
  return fetch(`/users/${id}`, {
    method: "PATCH",
    body: JSON.stringify(payload),
  });
}

心法:Partial 適合用在「PATCH」或「設定檔更新」這種欄位可選的情境。


5) Required<T>:把所有屬性改成必填

假設有一個表單型別:

ts
CopyEdit
type UserProfileForm = {
  name?: string;
  email?: string;
};

Required 強制全部必填:

ts
CopyEdit
type UserProfileRequired = Required<UserProfileForm>;


6) Record<K, T>:建立固定 key 集合的型別

ts
CopyEdit
type Role = "admin" | "editor" | "viewer";
type RolePermissions = Record<Role, string[]>;

const permissions: RolePermissions = {
  admin: ["create", "delete", "update"],
  editor: ["update"],
  viewer: ["read"],
};

實戰用途:權限表、設定檔映射、字典資料結構


7) ReturnType<T>:取得函式回傳型別

ts
CopyEdit
function getUser() {
  return { id: "u1", name: "Alice" };
}

type GetUserReturn = ReturnType<typeof getUser>;

搭配 API 呼叫:

ts
CopyEdit
async function fetchUser(id: string) {
  return { id, name: "Alice" };
}
type FetchUserResult = Awaited<ReturnType<typeof fetchUser>>;

心法:讓型別自動跟著函式改動,避免人手動同步。


8) 工具型別組合應用

更新 API 輸入型別:

ts
CopyEdit
type UpdateUserApiInput = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;

後端 Controller:

ts
CopyEdit
router.patch("/:id", async (req, res) => {
  const payload: UpdateUserApiInput = req.body;
  // Prisma update 操作...
});


9) 自訂工具型別(延伸)

你可以自己做工具型別,例如 Mutable<T>(移除 readonly):

ts
CopyEdit
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Nullable<T>(允許 null):

ts
CopyEdit
type Nullable<T> = { [P in keyof T]: T[P] | null };


10) 常見坑

  1. 工具型別只是編譯期的幫手,不會改變執行期資料
  2. 不要過度巢狀:可讀性會下降
  3. 型別過度抽象:有時候直接寫出明確型別更易懂

上一篇
Day 20|錯誤處理與例外型別化:用型別守住你的錯誤流
下一篇
Day 22|型別守衛與類型窄化:讓 TypeScript 幫你聰明收斂型別
系列文
我與型別的 30 天約定:TypeScript 入坑實錄24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言